package com.wang.tool.monitor;

import com.alibaba.fastjson.JSON;
import com.wang.tool.common.ErrorInfo;
import com.wang.tool.monitor.core.*;
import com.wang.tool.monitor.log.LogApi;
import com.wang.tool.monitor.log.LogInfo;
import com.wang.tool.monitor.log.SimpleLog;
import com.wang.tool.monitor.log.SimpleLogApi;
import com.wang.tool.monitor.translator.GlobalErrorTranslator;
import com.wang.tool.trace.TraceHolder;
import com.wang.tool.util.SimpleLogUtil;

import java.text.MessageFormat;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 链路日志工具,利于快速排查
 * 业务日志一般分为三大类
 * 1.普通记录日志,无固定格式要求,主要排查用
 *
 * @Author shaopeng
 * @see LinkLogUtil#info
 * @see LinkLogUtil#log
 * @see LinkLogUtil#error
 * 对于异常类型的对象的展示采用摘要信息的模式
 * @see LinkLogUtil#toString
 * 2.需要监控的日志,需要有固定格式,利于配置监控
 * @see LinkLogUtil#newTagLog
 * @see LinkLogUtil#newDigestLog
 * 3.链路日志,快速熟悉链路,排查问题
 * @see LinkLogUtil#newLinkLog
 * @see LinkLogUtil#newProfilerLog
 * <p>
 * tips:结合msgSupplier可避免最终无输出日志导致无谓的序列化性能损耗
 * tips:日志可监控部分保留字符  ;,:|()
 */
public class LinkLogUtil {

    /**
     * other
     */
    public static final String BIZ = "B";

    /**
     * info
     */
    public static final String INFO = "I";

    /**
     * warn
     */
    public static final String WARN = "W";

    /**
     * 摘要
     */
    public static final String DIGEST = "D";

    /**
     * 标签
     */
    public static final String TAG = "T";

    /**
     * 链路
     */
    public static final String LINK = "L";

    /**
     * 性能
     */
    public static final String PROFILER = "P";

    /**
     * 空占位符
     */
    public static final String EMPTY_PLACEHOLDER = "-";

    public static final String PROFILER_ = "profiler_";
    public static final String LINK_ = "link_";

    /**
     * api
     */
    private static LogApi logApi = new SimpleLogApi();

    /**
     * 格式化对象
     * 默认json化
     */
    private static LogContentBuilder contentBuilder = new LogContentBuilder() {
        @Override
        public String build(String format, Object... args) {
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    args[i] = LinkLogUtil.toString(args[i]);
                }
            }
            return MessageFormat.format(format, args);
        }

        @Override
        public String toString(Object obj) throws Throwable {
            if (obj == null) {
                return EMPTY_PLACEHOLDER;
            }
            if (obj instanceof Collection) {
                String str = ((Collection<?>) obj).stream().map((Function<Object, String>) LinkLogUtil::toString).collect(Collectors.joining(","));
                return "[" + str + "]";
            } else if (obj.getClass().isArray()) {
                //数组
                StringBuilder builder = new StringBuilder();
                builder.append("[");
                Object[] arrays = (Object[]) obj;
                for (int i = 0; i < arrays.length; i++) {
                    if (i != 0) {
                        builder.append(",");
                    }
                    builder.append(LinkLogUtil.toString(arrays[i]));
                }
                builder.append("]");
                return builder.toString();
            } else if (obj instanceof String || obj instanceof Enum || obj.getClass().isPrimitive() || obj.getClass().getPackage().getName().startsWith("java.")) {
                return obj.toString();
            } else if (obj instanceof Throwable) {
                //异常
                return GlobalErrorTranslator.translate((Throwable) obj).toString();
            }
            return JSON.toJSONString(obj);
        }
    };

    /*-------------------------分割线-------------------------*/


    /**
     * info日志
     *
     * @param format msg:{0}
     * @param args   填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void info(String format, Object... args) {
        if (format == null) {
            return;
        }
        log(INFO, () -> formatMsg(null, format, args));
    }

    /**
     * info日志
     *
     * @param attachment 附件信息 可选
     * @param format     msg:{0}
     * @param args       填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void info(Map<String, Object> attachment, String format, Object... args) {
        if (format == null) {
            return;
        }
        log(INFO, () -> formatMsg(attachment, format, args));
    }

    /**
     * warn日志
     *
     * @param format msg:{0}
     * @param args   填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void warn(String format, Object... args) {
        if (format == null) {
            return;
        }
        log(WARN, () -> formatMsg(null, format, args));
    }

    /**
     * warn日志
     *
     * @param attachment 附件信息 可选
     * @param format     msg:{0}
     * @param args       填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void warn(Map<String, Object> attachment, String format, Object... args) {
        if (format == null) {
            return;
        }
        log(WARN, () -> formatMsg(attachment, format, args));
    }

    /**
     * 自定义日志
     *
     * @param format msg:{0}
     * @param args   填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void log(String loggerName, String format, Object... args) {
        if (format == null) {
            return;
        }
        log(loggerName, () -> formatMsg(null, format, args));
    }

    /**
     * 自定义日志
     *
     * @param attachment 附件信息 可选
     * @param format     msg:{0}
     * @param args       填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void log(String loggerName, Map<String, Object> attachment, String format, Object... args) {
        if (format == null) {
            return;
        }
        log(loggerName, () -> formatMsg(attachment, format, args));
    }

    public static void log(String loggerName, Supplier<SimpleLog> msgSupplier) {
        try {
            LinkInfo linkInfo = LinkHolder.getCurrent();
            if (loggerName == null) {
                loggerName = BIZ;
            }
            Consumer<LogInfo> logger = logApi.getLogger(loggerName, linkInfo);
            if (logger == null) {
                return;
            }
            SimpleLog simpleLog = msgSupplier.get();
            if (simpleLog == null) {
                simpleLog = new SimpleLog();
            }
            logger.accept(new LogInfo(linkInfo, simpleLog));
        } catch (Throwable e) {
            SimpleLogUtil.error(loggerName + "-Error", e);
        }
    }

    /**
     * error日志
     * 形似:[currentLink][scene]
     *
     * @param errorCode 异常场景 excel解析失败
     * @param e         异常
     */
    public static void error(String errorCode, Throwable e) {
        error(errorCode, e, () -> formatMsg(null, "errorType:{0}, errorMsg:{1}", e.getClass().getSimpleName(), e.getMessage()));
    }

    /**
     * error日志
     * 形似:[currentLink][scene]
     *
     * @param errorCode 异常场景 excel解析失败
     * @param e         异常
     */
    public static void error(String errorCode, Throwable e, String format, Object... args) {
        error(errorCode, e, () -> formatMsg(null, format, args));
    }

    /**
     * error日志
     * 形似:[currentLink][scene]
     *
     * @param errorCode 异常场景 excel解析失败
     * @param e         异常
     */
    public static void error(String errorCode, Throwable e, Map<String, Object> attachment) {
        error(errorCode, e, () -> formatMsg(attachment, "errorType:{0}, errorMsg:{1}", e.getClass().getSimpleName(), e.getMessage()));
    }


    /**
     * error日志
     * 形似:[currentLink][scene] formatMsg
     *
     * @param scene   异常场景 eg：excel解析失败
     * @param e       异常
     * @param extInfo 扩展信息 可选
     * @param format  msg:{0}
     * @param args    填参
     * @see MessageFormat#format(String, Object...)
     */
    public static void error(String scene, Throwable e, Map<String, Object> extInfo, String format, Object... args) {
        if (format == null) {
            return;
        }
        error(scene, e, () -> formatMsg(extInfo, format, args));
    }

    public static void error(String scene, Throwable e, Supplier<SimpleLog> msgSupplier) {
        try {
            LinkInfo current = LinkHolder.getCurrent();
            SimpleLog simpleLog = msgSupplier.get();
            if (simpleLog == null) {
                simpleLog = new SimpleLog();
            }
            if (current != null) {
                current.markImplicitError();
            }
            logApi.error(e, scene, new LogInfo(current, simpleLog));
        } catch (Throwable ex) {
            SimpleLogUtil.error("E-Error", ex);
        }
    }


    public static String buildContent(String format, Object... args) {
        try {
            if (format == null || contentBuilder == null) {
                return null;
            }
            return contentBuilder.build(format, args);
        } catch (Throwable e) {
            return format;
        }
    }

    /**
     * 格式化日志
     *
     * @param attachment 附件信息 可选
     * @param format     format 可选
     * @param args       format参数 可选
     * @return
     * @see MessageFormat#format
     */
    public static SimpleLog formatMsg(Map<String, Object> attachment, String format, Object... args) {
        return new SimpleLog(buildContent(format, args), attachment);
    }


    /**
     * 取日志输出器
     *
     * @param loggerName 非空
     * @return 非空代表当前可用
     */
    public static Consumer<SimpleLog> getLogger(String loggerName) {
        if (logApi == null) {
            return null;
        }
        Consumer<LogInfo> logger = logApi.getLogger(loggerName == null ? BIZ : loggerName, LinkHolder.getCurrent());
        if (logger == null) {
            return null;
        }
        return new Consumer<SimpleLog>() {
            @Override
            public void accept(SimpleLog simpleLog) {
                logger.accept(new LogInfo(LinkHolder.getCurrent(), simpleLog));
            }
        };
    }


    /*-------------------------分割线-------------------------*/

    /**
     * 创建摘要日志
     *
     * @param group  建议非空
     * @param biz    建议非空
     * @param action 建议非空
     * @return
     */
    public static DigestLog newDigestLog(String group, String biz, String action) {
        DigestLog digestLog = new DigestLog();
        digestLog.setGroup(group);
        digestLog.setBiz(biz);
        digestLog.setAction(action);
        return digestLog;
    }

    /**
     * 创建标签日志
     *
     * @return
     */
    public static TagLog newTagLog(String tag) {
        TagLog tagLog = new TagLog();
        tagLog.setTag(tag);
        return tagLog;
    }


    /**
     * 创建链路日志
     *
     * @return
     */
    public static LinkLog newLinkLog(LinkInfo link) {
        LinkLog linkLog = new LinkLog();
        linkLog.setLink(link);
        return linkLog;
    }


    /**
     * 创建性能日志
     *
     * @return
     */
    public static ProfilerLog newProfilerLog(LinkInfo link) {
        ProfilerLog profilerLog = new ProfilerLog();
        profilerLog.setLink(link);
        return profilerLog;
    }



    /*-------------------------分割线-------------------------*/


    public static String currentLink(LinkInfo linkInfo) {
        return linkInfo == null ? "[-,-,-]" : ("[" + linkInfo.getBiz() + "," + linkInfo.getAction() + "," + linkInfo.getLinkid() + "]");
    }


    /**
     * null -> -
     * Throwable -> GlobalErrorTranslator#translate
     * java包/enum -> toString()
     * other -> toJSONString()
     *
     * @param obj
     * @return
     */
    public static String toString(Object obj) {
        if (obj == null) {
            return EMPTY_PLACEHOLDER;
        }
        if (contentBuilder == null) {
            return null;
        }
        try {
            return contentBuilder.toString(obj);
        } catch (Throwable e) {
            ErrorInfo descError = GlobalErrorTranslator.translate(e);
            descError.setErrorCode("toStringError");
            descError.setErrorMsg("字符化失败");
            return descError.toString();
        }
    }


    /*-------------------------分割线-------------------------*/


    /**
     * digest日志
     */
    public static class DigestLog {

        /**
         * 分组
         */
        private String group;

        /**
         * 双码-biz 业务域
         * 业务域 eg:接口类名
         */
        private String biz;

        /**
         * 双码-事情 干了啥
         * 什么事 eg:方法名
         */
        private String action;

        /**
         * 成功没
         * eg:有无异常抛出
         */
        private Boolean success;

        /**
         * 状态码,对结果的补充
         * eg:供给不足/部分成功
         */
        private String status;

        /**
         * 耗时
         */
        private Integer cost;

        /**
         * 性能指标
         */
        private PerformanceEnum performance;

        /**
         * 自定义摘要信息
         */
        private List<Digest> digests;

        /**
         * 标签集
         */
        private List<String> tags;

        /**
         * 附件信息
         * 详情部分
         */
        private Map<String, Object> attachment;


        /**
         * 指定链路
         */
        private LinkInfo pointLink;

        /**
         * 指定日志
         */
        private String pointLogger;


        public DigestLog success(Boolean success) {
            this.success = success;
            return this;
        }

        public DigestLog status(String status) {
            this.status = status;
            return this;
        }

        public DigestLog cost(Integer cost) {
            this.cost = cost;
            return this;
        }

        public DigestLog performance(PerformanceEnum performance) {
            this.performance = performance;
            return this;
        }


        /**
         * 指定链路
         *
         * @param pointLink
         * @return
         */
        public DigestLog pointLink(LinkInfo pointLink) {
            this.pointLink = pointLink;
            return this;
        }

        /**
         * 指定日志
         *
         * @param pointLogger
         * @return
         */
        public DigestLog pointLogger(String pointLogger) {
            this.pointLogger = pointLogger;
            return this;
        }

        /**
         * 添加摘要
         *
         * @param digest
         * @return
         */
        public DigestLog addDigest(String name, Object digest) {
            this.digests = this.digests != null ? this.digests : new ArrayList<>(4);
            this.digests.add(new Digest(name == null ? "" : name, digest == null ? EMPTY_PLACEHOLDER : digest.toString()));
            return this;
        }

        /**
         * 添加标签
         *
         * @param tag
         * @param v1
         * @param v2
         * @return
         */
        public DigestLog addTag(String tag, String v1, String v2) {
            this.tags = this.tags == null ? new ArrayList<>() : this.tags;
            this.tags.add((tag == null ? EMPTY_PLACEHOLDER : tag) + ':' + (v1 == null ? EMPTY_PLACEHOLDER : v1) + ':' + (v2 == null ? EMPTY_PLACEHOLDER : v2));
            return this;
        }

        /**
         * 添加附件
         *
         * @param key
         * @param val
         * @return
         */
        public DigestLog addAttachment(String key, Object val) {
            if (key == null) {
                return this;
            }
            val = val == null ? LinkLogUtil.EMPTY_PLACEHOLDER : val;
            attachment = attachment != null ? attachment : new LinkedHashMap<>(4);
            attachment.put(key, val);
            return this;
        }


        public void log() {
            try {
                LinkInfo linkInfo = pointLink != null ? pointLink : LinkHolder.getCurrent();
                Consumer<LogInfo> logger = logApi.getLogger(pointLogger == null ? DIGEST : pointLogger, linkInfo);
                if (logger == null) {
                    return;
                }
                logger.accept(new LogInfo(linkInfo, new SimpleLog(buildStr(linkInfo), attachment)));
            } catch (Throwable e) {
                SimpleLogUtil.error("digestError", e);
            }
        }


        @Override
        public String toString() {
            return buildStr(pointLink != null ? pointLink : LinkHolder.getCurrent());
        }

        /**
         * 摘要
         * 形似:[D][rootAction,id]group;biz;action,success,status,cost,performance;dk1|dv1;(debug:true:-)
         * 结果则为[D][rootAction,id]group;biz;action,success,status,cost,NL;用户id|1;(debug:true:-)
         */
        public String buildStr(LinkInfo linkInfo) {
            StringBuilder signStr = new StringBuilder(64);
            //自定义摘要-格式统一可监控
            if (digests != null) {
                for (Digest digest : digests) {
                    signStr.append(digest.getK()).append('|').append(digest.getV()).append(';');
                }
            }

            if (tags != null) {
                for (String tag : tags) {
                    signStr.append('(').append(tag).append(')');
                }
            }

            return MessageFormat.format("[D][{0},{1}]{2};{3};{4},{5},{6},{7},{8};{9}", linkInfo == null ? EMPTY_PLACEHOLDER : linkInfo.getRoot().bizAction(), linkInfo == null ? EMPTY_PLACEHOLDER : String.valueOf(linkInfo.getLinkid()), this.group, this.biz, this.action, success != null && success ? "Y" : "N", this.status == null ? EMPTY_PLACEHOLDER : this.status, this.cost == null ? EMPTY_PLACEHOLDER : String.valueOf(this.cost), this.performance == null ? EMPTY_PLACEHOLDER : this.performance.name(), signStr);
        }

        public void setBiz(String biz) {
            this.biz = biz;
        }

        public void setAction(String action) {
            this.action = action;
        }

        public void setSuccess(Boolean success) {
            this.success = success;
        }

        public void setStatus(String status) {
            this.status = status;
        }

        public void setCost(Integer cost) {
            this.cost = cost;
        }

        public String getBiz() {
            return biz;
        }

        public String getAction() {
            return action;
        }

        public Boolean getSuccess() {
            return success;
        }

        public String getStatus() {
            return status;
        }

        public Integer getCost() {
            return cost;
        }

        public List<Digest> getDigests() {
            return digests;
        }

        public void setDigests(List<Digest> digests) {
            this.digests = digests;
        }

        public Map<String, Object> getAttachment() {
            return attachment;
        }

        public void setAttachment(Map<String, Object> attachment) {
            this.attachment = attachment;
        }

        public String getGroup() {
            return group;
        }

        public void setGroup(String group) {
            this.group = group;
        }

        public List<String> getTags() {
            return tags;
        }

        public void setTags(List<String> tags) {
            this.tags = tags;
        }

        public PerformanceEnum getPerformance() {
            return performance;
        }

        public void setPerformance(PerformanceEnum performance) {
            this.performance = performance;
        }
    }

    /**
     * tag日志
     */
    public static class TagLog {

        /**
         * 标签码
         */
        private String tag;

        /**
         * 值1
         */
        private String v1;

        /**
         * 值2
         */
        private String v2;


        /**
         * 附件信息
         * 详情
         */
        private Map<String, Object> attachment;

        /**
         * 指定链路
         */
        private LinkInfo pointLink;

        /**
         * 指定日志
         */
        private String pointLogger;

        /**
         * 标签1
         *
         * @param v1
         * @return
         */
        public TagLog v1(String v1) {
            this.v1 = v1;
            return this;
        }

        /**
         * 标签2
         *
         * @param v2
         * @return
         */
        public TagLog v2(String v2) {
            this.v2 = v2;
            return this;
        }


        /**
         * 指定链路
         *
         * @param pointLink
         * @return
         */
        public TagLog pointLink(LinkInfo pointLink) {
            this.pointLink = pointLink;
            return this;
        }

        /**
         * 指定日志
         *
         * @param pointLogger
         * @return
         */
        public TagLog pointLogger(String pointLogger) {
            this.pointLogger = pointLogger;
            return this;
        }

        /**
         * 添加附件
         * 详情
         *
         * @param key
         * @param val
         * @return
         */
        public TagLog addAttachment(String key, Object val) {
            if (key == null || val == null) {
                return this;
            }
            attachment = attachment != null ? attachment : new LinkedHashMap<>(4);
            attachment.put(key, val);
            return this;
        }


        public void log() {
            try {
                LinkInfo linkInfo = pointLink == null ? LinkHolder.getCurrent() : pointLink;
                Consumer<LogInfo> logger = logApi.getLogger(pointLogger == null ? TAG : pointLogger, linkInfo);
                if (logger == null) {
                    return;
                }
                //记录
                if (linkInfo != null) {
                    linkInfo.addTag(tag, v1, v2);
                }
                logger.accept(new LogInfo(linkInfo, new SimpleLog(toString(), attachment)));
            } catch (Throwable e) {
                SimpleLogUtil.error("tagError", e);
            }
        }

        /**
         * 形似:tag:v1:v2
         * 结果则为:坐标转换失败:未查到:-
         * @return
         */
        @Override
        public String toString() {
            return MessageFormat.format("{0}:{1}:{2}", tag == null ? EMPTY_PLACEHOLDER : tag, v1 == null ? EMPTY_PLACEHOLDER : v1, v2 == null ? EMPTY_PLACEHOLDER : v2);
        }

        public void setTag(String tag) {
            this.tag = tag;
        }

        public String getTag() {
            return tag;
        }

        public String getV2() {
            return v2;
        }

        public void setV2(String v2) {
            this.v2 = v2;
        }

        public Map<String, Object> getAttachment() {
            return attachment;
        }

        public void setAttachment(Map<String, Object> attachment) {
            this.attachment = attachment;
        }

        public String getV1() {
            return v1;
        }

        public void setV1(String v1) {
            this.v1 = v1;
        }

        public LinkInfo getPointLink() {
            return pointLink;
        }

        public void setPointLink(LinkInfo pointLink) {
            this.pointLink = pointLink;
        }
    }


    public static class LinkLog {

        private LinkInfo link;

        private String msg;

        /**
         * 指定日志
         */
        private String pointLogger;

        /**
         * 指定日志
         *
         * @param pointLogger
         * @return
         */
        public LinkLog pointLogger(String pointLogger) {
            this.pointLogger = pointLogger;
            return this;
        }

        public LinkLog msg(String format, Object... args) {
            this.msg = format == null ? "" : buildContent(format, args);
            return this;
        }

        public LinkInfo getLink() {
            return link;
        }

        public String getMsg() {
            return msg;
        }

        public void setLink(LinkInfo link) {
            this.link = link;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }

        public void log() {
            try {
                if (link == null) {
                    return;
                }
                Consumer<LogInfo> logger = logApi.getLogger(pointLogger == null ? LINK : pointLogger, link);
                if (logger == null) {
                    return;
                }
                List<Digest> digests = link.getDigests();
                if (digests==null || digests.stream().allMatch(new Predicate<Digest>() {
                    @Override
                    public boolean test(Digest digest) {
                        return !MonitorInfo.REQ_ID.equals(digest.getK());
                    }
                })) {
                    String reqId = TraceHolder.getReqId();
                    if (reqId!=null) {
                        link.addDigest(MonitorInfo.REQ_ID, reqId);
                        link.addDigest(MonitorInfo.TIME, MonitorInfo.formatTimeWithZone(link.getBeginTime()));
                    }
                }

                HashMap<String, Object> map = new HashMap<>(4);
                map.put(LINK_, JSON.toJSONString(link));
                logger.accept(new LogInfo(link, new SimpleLog(toString(), map)));
            } catch (Throwable e) {
                SimpleLogUtil.error("linkError", e);
            }
        }

        /**
         * 链路
         */
        @Override
        public String toString() {
            return msg == null ? "" : msg;
        }
    }


    public static class ProfilerLog {

        private LinkInfo link;

        private String msg;

        /**
         * 指定日志
         */
        private String pointLogger;

        /**
         * 指定日志
         *
         * @param pointLogger
         * @return
         */
        public ProfilerLog pointLogger(String pointLogger) {
            this.pointLogger = pointLogger;
            return this;
        }

        public ProfilerLog msg(String format, Object... args) {
            this.msg = format == null ? "" : buildContent(format, args);
            return this;
        }

        public LinkInfo getLink() {
            return link;
        }

        public String getMsg() {
            return msg;
        }

        public void setLink(LinkInfo link) {
            this.link = link;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }

        public void log() {
            try {
                if (link == null) {
                    return;
                }
                Consumer<LogInfo> logger = logApi.getLogger(pointLogger == null ? PROFILER : pointLogger, link);
                if (logger == null) {
                    return;
                }
                List<Digest> digests = link.getDigests();
                if (digests==null || digests.stream().allMatch(new Predicate<Digest>() {
                    @Override
                    public boolean test(Digest digest) {
                        return !MonitorInfo.REQ_ID.equals(digest.getK());
                    }
                })) {
                    String reqId = TraceHolder.getReqId();
                    if (reqId!=null) {
                        link.addDigest(MonitorInfo.REQ_ID, reqId);
                        link.addDigest(MonitorInfo.TIME, MonitorInfo.formatTimeWithZone(link.getBeginTime()));
                    }
                }
                HashMap<String, Object> map = new HashMap<>(4);
                map.put(PROFILER_, link.toProfilerString());
                logger.accept(new LogInfo(link, new SimpleLog(toString(), map)));
            } catch (Throwable e) {
                SimpleLogUtil.error("profilerError", e);
            }
        }

        /**
         * 性能日志
         */
        @Override
        public String toString() {
            return msg == null ? "" : msg;
        }
    }


    public static LogApi getLogApi() {
        return logApi;
    }

    public static void setLogApi(LogApi logApi) {
        LinkLogUtil.logApi = logApi == null ? new LogApi() {
            @Override
            public Consumer<LogInfo> getLogger(String loggerName, LinkInfo linkInfo) {
                return null;
            }

            @Override
            public void error(Throwable e, String errorCode, LogInfo logInfo) {
                SimpleLogUtil.BIZ_LOGGER.error("[-] " + errorCode + logInfo.getContent(), e);
            }

        } : logApi;
        SimpleLogUtil.info("[changeLogApi] logApi:" + logApi);
    }

    public static LogContentBuilder getContentBuilder() {
        return contentBuilder;
    }

    public static void setContentBuilder(LogContentBuilder contentBuilder) {
        LinkLogUtil.contentBuilder = contentBuilder == null ? new LogContentBuilder() {
            @Override
            public String build(String format, Object... args) throws Throwable {
                return null;
            }

            @Override
            public String toString(Object obj) throws Throwable {
                return null;
            }
        } : contentBuilder;
        SimpleLogUtil.info("[changeLogContentBuilder] contentBuilder:" + contentBuilder);
    }

    /**
     * 日志内容构建器
     */
    public static interface LogContentBuilder {
        /**
         * @param format 非空
         * @param args
         * @return
         * @throws Throwable 异常会被catch
         */
        String build(String format, Object... args) throws Throwable;

        /**
         * 对象转字符串
         *
         * @param obj 非空
         * @return
         * @throws Throwable
         */
        String toString(Object obj) throws Throwable;
    }
}
